<?php

class Yz_Image_Default
    extends Yz_Class
    implements Yz_Image_Interface
{

    /**
     *  @var image-resource
     */
    protected $resource;

    /**
     *  @var int
     */
    protected $height;

    /**
     *  @var int
     */
    protected $width;

    /** supported image formats
     *  @var array
     */
    protected static $types = array (
        IMAGETYPE_JPEG => 'jpeg',
        IMAGETYPE_GIF  => 'gif',
        IMAGETYPE_PNG  => 'png',
    );

    /****************************
     *      Public methods      *
     ****************************/

    /**
     *  @param Yz_File
     *  @return Yz_Image_Default
     *  @throw Yz_Exception_FileNotFound
     *  @throw Yz_Image_Exception
     */
    public static function fromFile(Yz_File $file)
    {
        if (! $file->isFile()) {
            throw Yz_Exception_FileNotFound::_new(array('filename' => "$file"));
        }
        $s = getimagesize("$file");
        if( false === $s ) {
            // invalid image
            throw new Yz_Image_Exception("File '{$file}' is not an image file");
        }
        list( $w, $h, $t, $attrs ) = $s;
        if( ! isset( static::$types[ $t ] ) ) {
            // unknown/unhandled format
            throw new Yz_Image_Exception("File '{$file}' is not of supported image file format");
        }

        // choose opener function
        $opener = 'imagecreatefrom' . static::$types[ $t ];
        if (! function_exists($opener)) {
            throw new Yz_Image_Exception("Image extension not installed");
        }
        $res = $opener("$file");
        if (! $res) {
            throw new Yz_Image_Exception("File '{$file}' could not be opened as an image");
        }
        $i = new static($res, $w, $h);
        return $i;
    }

    /**
     *  @param int width
     *  @param int height
     *  @return Yz_Image_Default
     */
    public static function create($width, $height)
    {
        $width  = Yz_Int::testPositive($width);
        $height = Yz_Int::testPositive($height);
        $res = imagecreatetruecolor($width, $height);
        $i = new static($res, $width, $height);
        return $i;
    }

    /**
     *
     */
    public function __clone()
    {
        throw new Yz_Image_Exception("%CLONE%");
        // TODO copy image
        //$this->resource = clone $this->resource;
    }

    /**
     *
     */
    protected function __construct($resource, $width, $height)
    {
        $this->resource = $resource;
        $this->width    = $width;
        $this->height   = $height;
    }

    /**
     *
     */
    public function getHeight()
    {
        return $this->height;
    }

    /**
     *
     */
    public function getWidth()
    {
        return $this->width;
    }

    /** insert a watermark from file, at the specified coordinates
     *      if x or y is positive, it is the pixel offset from left (top)
     *      if negative, the pixel offset counts from right (bottom).
     *      Coordinates are rounded, so to place the watermark at bottom right corner, use {-0.1, -0.1} or similar.
     *  @param Yz_File $watermarkFile
     *  @param int x
     *  @param int y
     */
    public function insertWatermark(Yz_File $watermarkFile, $x, $y)
    {
        return $this;
    }


    /** resize image to new dimensions
     *  @param int $w width
     *  @param int $h height
     *  @return Yz_Image_Default new instance
     *  @throw Image_Exception
     */
    public function resizeTo($w, $h)
    {
        $w = Yz_Int::testPositive($w);
        $h = Yz_Int::testPositive($h);
        return $this->doResize($w, $h);
    }

    /** resize image proportionally so its dimensions be within WxH, optionally enlarge
     *  Null for width or height means that this dimension is not constrained
     *  @param int|null $w width
     *  @param int|null $h height
     *  @param bool $enlarge
     *  @return Yz_Image_Default new instance
     *  @throw Image_Exception
     */
    public function resizeWithin($w, $h, $enlarge=false)
    {
        $w = (null === $w) ? 0 : Yz_Int::testPositive($w);
        $h = (null === $h) ? 0 : Yz_Int::testPositive($h);
        if (0 === $w && 0 === $h) {
            throw new Yz_Exception_InvalidArgument('Width and height can not be both null');
        }
        $w0 = $this->width;
        $h0 = $this->height;
        debug("W0xH0 = {$w0}x{$h0}, WxH = {$w}x{$h}");
        $dw = $w/$w0;
        $dh = 0 === $h ? $dw : $h/$h0;
        // set the multiplier to the smallest (non-zero) proportion of the two
        if ($dw > 0 && $dw <= $dh) {
            $mult = $dw; // w/w0
            $byWidth = true;
        } else {
            $mult = $dh; // h/h0
            $byWidth = false;
        }
        if ($mult >= 1 && ! $enlarge) {
            // the dimensions are already within constraints,
            // enlargement not requested => do nothing
            debug("Image not resized: W0xH0 = {$w0}x{$h0}, final WxH = {$w}x{$h}, enlarge disabled");
            return $this;
        }
        $byWidth
            ? ( $h = round($h0*$dw) )
            : ( $w = round($w0*$dh) )
        ;
        debug("W0xH0 = {$w0}x{$h0}, final WxH = {$w}x{$h}");
        return $this->doResize($w, $h);
    }

    /**
     *  @param Yz_File
     *  @return this
     *  @throw Yz_Image_Exception
     */
    public function saveToFile(Yz_File $file)
    {
        $ext = strtolower($file->getExtension());
        switch ($ext) {
            case 'gif':
                $writer = 'imagegif';
                break;
            case 'png':
                $writer = 'imagepng';
                break;
            case '':        // empty extension, assuming JPEG
            case 'jpg':
            case 'jpeg':
                $writer = 'imagejpeg';
                break;
            default:
                throw new Yz_Image_Exception('Unknown file extension');
        }
        if (! function_exists($writer)) {
            throw new Yz_Image_Exception("Image extension not installed");
        }
        $r = $writer( $this->resource, "$file" );
        if (! $r) {
            throw Yz_Exception_FileNotWritable::_new(array('filename'=>"$file"));
        }
        return $this;
    }

    /*******************************
     *      Protected methods      *
     *******************************/

    /**
     *  @return image resource
     */
    protected function doResize($w, $h)
    {
        $res = imagecreatetruecolor($w, $h);
        $w0 = $this->width;
        $h0 = $this->height;
        debug("Resize from {$w0}x{$h0} to {$w}x{$h}");
        $r = imagecopyresampled(
            $res,               // new resource
            $this->resource,    // old resource
            0, 0, 0, 0,         // upper left corner
            $w, $h,             // new width, height
            $w0, $h0            // original width, height
        );
        if (! $r) {
            throw new Yz_Image_Exception("Failed to resize image: {$w0}x{$h0} => {$w}x{$h}");
        }
        return new static($res, $w, $h);
    }

}
